# coding=utf8
import hashlib
import json
import os

import requests
import allure
from urllib.parse import urlencode
import bs4
from jsonpath import jsonpath
from requests import Session
from common.logger import Logger
from common.read_yaml import ReadYaml_By_Root_Dict


# from common.utils import execute_env


class HttpClient:
    def __init__(self, session: Session = None):
        try:
            # 切换环境
            self.env_flag = os.environ['api_test_env']
        except:
            self.env_flag = 'single'
            # self.env_flag= 'saas_myspace_test'
        self.global_param = Read_Environment_info().environment(self.env_flag)
        self.host = self.global_param['host']
        self.headers = {'Content-Type': 'application/x-www-form-urlencoded',
                        'X-Requested-With': 'XMLHttpRequest'}
        # 一次登录公用一个session，进行session共用
        if session is None:
            self.session = requests.session()
        else:
            self.session = session

    def send(self, url, body='', payload='', method='', headers=None, X_CSRF_Token='', enable_vtk=0, verify=False):
        """
        发送请求
        :param url: 传入相对路径，如：/api/flowinstance/approval/get-process-instance-info
        :param body: 参数传入形式：businessGuid=bef35b58-f2af-431b-0d9b-08d98c9079eb
        :param payload: 参数传入形式：{"businessGuid": "bef35b58-f2af-431b-0d9b-08d98c9079eb"}
        :param method: get,post
        :param headers: 请求头
        :param X_CSRF_Token: 流程中心token
        :param enable_vtk: 是否启用入参加密，默认不启用，特定请求接口需要启用
        :return:
        """
        # 不能用self.headers.update(headers)，否则最后header变成None了，感觉有bug
        if headers is None:
            headers = {}
        if headers:
            for k, v in headers.items():
                self.headers[k] = v
        headers = self.headers
        if X_CSRF_Token:
            headers["X-CSRF-Token"] = X_CSRF_Token.strip('"')  # strip() 方法用于移除字符串头尾指定的字符（默认为空格或换行符）或字符序列。
        # 判断URL是否为完整URL，然后再进行请求
        if enable_vtk!=0:
            headers["vtk"] = hashlib.md5(json.dumps(payload).encode('utf8')).hexdigest()
        if url.startswith('http://') or url.startswith('https://'):
            full_url = url
        else:
            full_url = self.get_full_url(url)

        self.create_request_log(full_url, method, body, payload, headers)

        if method.lower()=="get":
            # response =  requests.get(url,params = body,headers = headers)
            response = self.session.get(full_url, data=body, headers=headers, verify=verify)
        elif method.lower()=="post":
            # response =  requests.post(url,data = body,headers=headers)
            response = self.session.post(full_url, data=body, json=payload, headers=headers, verify=verify)
        self.create_response_log(response.status_code, response.text)
        return response
        # return {"status_code": response.status_code, "data": response.json()}

    def get_token(self, url):
        """
        url: 每个页面进去都会初始化一个X-CSRF-Token，初次进入需要调用此接口，获取到csrf-token的值
        """
        resp = self.send(url, method='get')
        try:
            csrf_token = self.get_html_attr_val(resp.text, '[name="csrf-token"]', 'content', 0)
        except:
            print('token获取失败')
            csrf_token = ''
        return csrf_token

    def get_full_url(self, url, etc: dict = None, replace={}, h=""):
        # 先判断是否为saas环境，不是saas环境，则无需租户id和appcode
        if 'saas' in self.env_flag.lower():
            __app_tenant_id = self.global_param.get("app_tenant_id")
            __app_code = self.global_param.get("app_code")
            if url.startswith('/api'):  # 判断是否为外部接口api，参数通过params接入url
                tenant_info = {"__app_tenant_id": __app_tenant_id,
                               "__app_code": __app_code}
            else:  # 不为外部接口，直接将租户id和code接入url前面
                url = f'/{__app_tenant_id}/{__app_code}{url}'
                tenant_info = {}
        else:
            tenant_info = {}
        if h:
            host = h.rstrip('/')  # rstrip() 删除 string 字符串末尾的指定字符（默认为空格）.
        else:
            host = self.host.rstrip('/')

        url = url.lstrip('/')  # lstrip() 方法用于截掉字符串左边的空格或指定字符。
        full_url = host + "/" + url
        # full_url += "?platform=zhengshi&time=" + str(int(round(time.time() * 1000)))
        if etc is None:
            etc = {}
        etc.update(tenant_info)
        if len(etc):
            s = urlencode(etc)  # urlencode  urllib库里面有个urlencode函数，可以把key-value这样的键值对转换成我们想要的格式，返回的是a=1&b=2这样的字符串
            if full_url.count('?')==1:
                full_url += "&" + s  # 原始url有初始参数时，先加&再加k-v
            else:
                full_url += "?" + s  # 原始url没有初始参数，且加入了租户信息，在链接后面加?再加k-v

        if len(replace):
            full_url = str.format(full_url,
                                  *replace)  # str.format() 方法通过字符串中的花括号 {} 来识别替换字段 replacement field，从而完成字符串的格式化。
        return full_url

    def tags_val(self, tag, key='', index=0):
        '''
        return html tag list attribute @key @index
        if @key is empty, return tag content
        '''
        if len(tag)==0 or len(tag) <= index:
            return ''
        elif key.lower()=='text':
            txt = tag[index].text
            return txt.strip(' \t\r\n') if txt else ''
        elif key.lower()=='contents':
            txt = tag[index].contents
            return txt
        elif key:
            txt = tag[index].get(key)
            return txt

    def tag_val(self, tag, key=''):
        '''
        return html tag attribute @key
        if @key is empty, return tag content
        '''
        if tag is None:
            return ''
        elif key:
            txt = tag.get(key)
            return txt.strip(' \t\r\n') if txt else ''
        else:
            txt = tag.text
            return txt.strip(' \t\r\n') if txt else ''

    def get_html_attr_val(self, resp_html, css_locator, attribute='', index=0):
        """
        获取html中某个tag中的属性值
        resp_html: 请求返回的html代码
        css_locator：css元素定位方式
        attribute：需要获取的元素对应的属性，比如获取元素class，value，text，或者属性name的值，不传参则获取html段
        index：如果有返回多个元素，需要获取第几个，默认为0，获取第一个元素
        """
        soup = bs4.BeautifulSoup(resp_html, 'lxml')
        tags = soup.select(css_locator)
        attribute_value = self.tags_val(tags, attribute, index)
        return attribute_value

    def jsonpath_value(self, json_obj, expr, index=0):
        """
        通过jsonpath精确匹配唯一的值，如果jsonpath的值不存在，则会断言失败。
        :param json_obj: json对象
        :param expr: jsonpath表达式
        :param index: 取值索引
        :return: 返回jsonpath找到的值
        """
        if jsonpath(json_obj, expr)==False:
            Logger.error(f"jsonpath表达式【{expr}】在目标json中无法找到：【{json_obj}】")
            assert False, f"jsonpath表达式【{expr}】在目标json中无法找到：【{json_obj}"
        return jsonpath(json_obj, expr)[index]

    @allure.step("请求日志")
    def create_request_log(self, url, method, body, json_str, header):
        if body:
            Logger.info(f'\n1、请求地址：{method} {url}\n2、请求体：{body}')
        elif json_str:
            Logger.info(f'\n1、请求地址：{method} {url}\n2、请求体：{json.dumps(json_str)}')
        else:
            Logger.info(f'\n1、请求地址：{method} {url}')

    # print("请求日志",url,method,body,header)
    @allure.step('响应日志')
    def create_response_log(self, status_code, text):
        if status_code!=200:
            Logger.error(f'\n3、响应状态：{status_code}\n响应内容：{text}')
    # print("响应日志",status_code,text)


class Read_Environment_info:
    """
    读取配置环境信息，用于确定测试哪个环境，可以通过读Jenkins配置的额外参数，来决定环境信息，Jenkins参数名：api_test_env
    """
    data = ReadYaml_By_Root_Dict("configs/environment_var.yml").get_yaml_data()  # 读取数据

    def environment(self, default_env='single'):
        """
        是否启jenkins参数。先读Jenkins参数，不存在则使用默认的test_env
        :param default_env: data.yml中的主key
        :return: 返回指定环境参数dict
        """
        try:
            # 如果读不到os.environ["api_test_env"]，则会报KeyError，
            test_env = os.environ["api_test_env"]
            # 读到为None或者为""，则执行默认环境。
            if test_env is not None and test_env!='':
                try:
                    # 读取Jenkins传入的环境关键key是否存在(data[test_env]中如果test_env不存在，会报KeyError)，不存在则读取默认
                    exec_env: dict = self.data[test_env]
                    # Logger.info(f'使用{test_env}环境执行接口测试')
                    return exec_env
                except KeyError:
                    Logger.info(f'{test_env}环境key在data.yml中不存在，使用默认参数执行')
                    # 读取默认环境关键字，如果读取默认环境也是KeyError，则断言失败，退出
                    return self._read_env_key(default_env)
            else:
                # 读取默认环境关键字，如果读取默认环境也是KeyError，则断言失败，退出
                return self._read_env_key(default_env)
        except KeyError:
            # Logger.info("【os.environ】不存在api_test_env参数！")
            return self._read_env_key(default_env)

    def _read_env_key(self, default_env):
        """
        读取默认环境key
        :param default_env:
        :return: 返回默认环境信息
        """
        try:
            exec_env: dict = self.data[default_env]
            Logger.info(f'使用{default_env}环境执行接口测试')
            return exec_env
        except KeyError:
            Logger.error(f'{default_env}环境key在data.yml中不存在，退出执行')
            assert False, f'{default_env}环境key在data.yml中不存在，退出执行'
